package com.me.test.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 两种使用方法
 * 1 建议使用多例 然后注入的形式
 *
 * @Bean
 * @Scope("prototype") public HttpRestUtils httpRestUtils(){
 * return HttpRestUtils.getDefaultInstance();
 * }
 * @Autowired private HttpRestUtils httpRestUtils;
 * 2 如果没有spring则直接在成员变量上
 * private static final HttpRestUtils HTTP_REST_UTILS=HttpRestUtils.getDefaultInstance();
 * <p>
 * <p>
 * 这里需要注意的地方：不要在每个方法实例化一封这样会造成资源浪费
 */
public class HttpRestUtils {

    // private static final HttpRestUtils HTTP_REST_UTILS = HttpRestUtils.getDefaultInstance();

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpRestUtils.class);

    private final HttpClient httpClient;


    private HttpRestUtils() {
        RequestConfig defaultRequestConfig = RequestConfig.custom()
                .setSocketTimeout(20000)
                .setConnectTimeout(20000)
                .setConnectionRequestTimeout(20000)
                .build();
        httpClient = HttpClients.custom()
                .setDefaultRequestConfig(defaultRequestConfig)
                .setMaxConnTotal(500)
                .setMaxConnPerRoute(100)
                //重试1次
                .setRetryHandler(new DefaultHttpRequestRetryHandler(5, false))
                .build();
        LOGGER.info("httpClient初始化完成");
    }



    private HttpRestUtils(HttpClient httpClient) {
        this.httpClient = httpClient;
        LOGGER.info("httpClient初始化完成");
    }

    /**
     * 得到默认实例
     *
     * @return
     */
    public static HttpRestUtils getDefaultInstance() {
        return new HttpRestUtils();
    }

    /**
     * 得到自定义实例
     *
     * @param httpClient
     * @return
     */
    public static HttpRestUtils getInstance(HttpClient httpClient) {
        return new HttpRestUtils(httpClient);
    }

    /**
     * @param url
     * @param params
     * @param headers
     * @param clazz List.class或者 new TypeReference<Response<HashMap<Integer,String>>>() {}.getType();
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> Pair<Integer, T> get(String url,
                                    List<NameValuePair> params,
                                    Map<String, String> headers,
                                    Class<T> clazz) {
        HttpResponse response = null;
        try {
            if (params != null && params.size() > 0) {
                String collect = params.parallelStream().map(pair -> pair.getName() + "=" + pair.getValue()).collect(Collectors.joining("&"));

                if (url.contains("?")) {
                    url = url + collect;
                } else {
                    url =  url+ "?" + collect;
                }
            }

            HttpGet httpGet = new HttpGet(url);
            httpGet.setHeader("Content-Type", "application/json");

            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpGet.setHeader(entry.getKey(), entry.getValue());
                }
            }

            response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz == null || clazz == String.class || clazz == Object.class || clazz == void.class || clazz == Void.class) {
                t = (T) result;
            } else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    public <T> Pair<Integer, T> post(String url,
                                     List<NameValuePair> params,
                                     Map<String, String> headers,
                                     Class<T> clazz) {
        HttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);

            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                }
            }

            if (params != null && !params.isEmpty()) {
                httpPost.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
            }

            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz ==null || clazz == String.class || clazz == Object.class || clazz == void.class || clazz==Void.class) {
                t = (T)result;
            }else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    public <T> Pair<Integer, T> post(String url,
                                     Map<String, String> params,
                                     Map<String, String> headers,
                                     Class<T> clazz) {
        HttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);

            httpPost.setHeader("Content-Type", "application/json");
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                }
            }

            if (params != null) {
                httpPost.setEntity(new StringEntity(MAPPER.writeValueAsString(params), StandardCharsets.UTF_8));
            }

            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz ==null || clazz == String.class || clazz == Object.class || clazz == void.class || clazz==Void.class) {
                t = (T)result;
            }else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    public <T> Pair<Integer, T> post(String url,
                                     String params,
                                     Map<String, String> headers,
                                     Class<T> clazz) {
        HttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);

            httpPost.setHeader("Content-Type", "application/json");
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                }
            }

            if (params != null) {
                httpPost.setEntity(new StringEntity(params));
            }

            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz ==null || clazz == String.class || clazz == Object.class || clazz == void.class || clazz==Void.class) {
                t = (T)result;
            }else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    public <T> Pair<Integer, T> upFile(String url,
                                     Pair<String, File> filePair,
                                     Map<String, String> headers,
                                     Class<T> clazz) {
        HttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);

            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                }
            }

            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
            multipartEntityBuilder.addBinaryBody(filePair.getLeft(), filePair.getRight());
            HttpEntity httpEntity = multipartEntityBuilder.build();
            httpPost.setEntity(httpEntity);

            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz ==null || clazz == String.class || clazz == Object.class || clazz == void.class || clazz==Void.class) {
                t = (T)result;
            }else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    public <T> Pair<Integer, T> put(String url,
                                    Object params,
                                    Map<String, String> headers,
                                    Class<T> clazz) {
        HttpResponse response = null;
        try {
            HttpPut httpPut = new HttpPut(url);
            if (params != null) {
                httpPut.setEntity(new StringEntity(params instanceof String ? params.toString() : MAPPER.writeValueAsString(params), StandardCharsets.UTF_8));
            }

            httpPut.setHeader("Content-Type", "application/json");
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpPut.setHeader(entry.getKey(), entry.getValue());
                }
            }

            response = httpClient.execute(httpPut);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz ==null || clazz == String.class || clazz == Object.class || clazz == void.class  || clazz==Void.class) {
                t = (T)result;
            }else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    public <T> Pair<Integer, T> delete(String url,
                                       Object params,
                                       Map<String, String> headers,
                                       Class<T> clazz) {
        HttpResponse response = null;
        try {
            HttpDeleteWithBody httpDelete = new HttpDeleteWithBody(url);
            if (params != null) {
                httpDelete.setEntity(new StringEntity(params instanceof String ? params.toString() : MAPPER.writeValueAsString(params), StandardCharsets.UTF_8));
            }

            httpDelete.setHeader("Content-Type", "application/json");
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpDelete.setHeader(entry.getKey(), entry.getValue());
                }
            }
            response = httpClient.execute(httpDelete);
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, StandardCharsets.UTF_8);

            T t;
            if (clazz ==null || clazz == String.class || clazz == Object.class || clazz == void.class  || clazz==Void.class) {
                t = (T)result;
            }else {
                t = MAPPER.readValue(result, clazz);
            }
            return Pair.of(response.getStatusLine().getStatusCode(), t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (response instanceof Closeable) {
                try {
                    ((Closeable) response).close();
                } catch (IOException e) {
                }
            }
        }
    }

    static class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {
        public static final String METHOD_NAME = "DELETE";
        @Override
        public String getMethod() {
            return METHOD_NAME;
        }

        public HttpDeleteWithBody() {
            super();
        }

        public HttpDeleteWithBody(final String uri) {
            super();
            setURI(URI.create(uri));
        }

        public HttpDeleteWithBody(final URI uri) {
            super();
            setURI(uri);
        }

    }

    public static void main(String[] args) throws JsonProcessingException {
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("name", "&"));
        params.add(new BasicNameValuePair("age", "18"));
        System.out.println(MAPPER.writeValueAsString(params));
    }

}
